
"""
gal v0.01 

ga-bitbot system launcher

Copyright 2011 Brian Monkaba

This file is part of ga-bitbot.

    ga-bitbot is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ga-bitbot is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ga-bitbot.  If not, see <http://www.gnu.org/licenses/>.
"""

__appversion__ = "0.01a"
print "ga-bitbot system launcher v%s"%__appversion__

import atexit
import sys
from subprocess import check_output as call, Popen, PIPE
import shlex
from os import environ
import os
from time import *
import hashlib
import random
import __main__

from load_config import *


def make_pid():
	#simple function which spits out a random hex code
	#which are used to set globaly unique process ids to spawned clients
	#generate a unique gene pool id
	md = hashlib.md5()
	md.update(str(time()) + str(random.random() * 1000000))
	return md.hexdigest()[0:16]
	


print "\n\tcommand line options:\n\t\tserver\t\tlaunches only the server components\n\t\tclient\t\tlaunches only the client components\n\t\tall\t\tlaunches all components"
print "\n\tthe default configuration will launch all components"
run_client = 1
run_server = 1
if len(sys.argv) >= 2:
	if sys.argv[1] == 'all':
		run_client = 1
		run_server = 1
		print "launching all components"
	if sys.argv[1] == 'client':
		run_client = 1
		run_server = 0
		print "launching client components only"
	if sys.argv[1] == 'server':
		run_client = 0
		run_server = 1
		print "launching server components only"
else:
	print "launching all components"
	sleep(3)

#the variable values below are superceded by the configuration loaded from the 
#configuration file global_config.json
#!!!!!!!! to change the values edit the json configuration file NOT the variables below !!!!!!!!
WATCHDOG_TIMEOUT = 60 #seconds
MONITORED_PROCESS_LAUNCH_TIMEOUT = 20 #seconds
GENE_SERVER_STDERR_FILE = "/dev/null"
BCFEED_STDERR_FILE = "/dev/null"
WC_SERVER_STDERR_FILE = "/dev/null"
REPORT_GEN_STDERR_FILE = "/dev/null"
GTS_STDERR_FILE = "/dev/null"
config_loaded = False
#load config
try:
	__main__ = load_config_file_into_object('global_config.json',__main__)
except:
	print "Error detected while loading the configuration. The application will now exit."
	import sys
	sys.exit()
else:
	if config_loaded == False:
		print "Configuration failed to load. The application will now exit."
		import sys
		sys.exit()
	else:
		print "Configuration loaded."

#open a null file to redirect stdout/stderr from the launched subprocesses 
fnull = open(os.devnull,'w')

if GENE_SERVER_STDERR_FILE == "/dev/null":
	GENE_SERVER_STDERR_FILE = fnull
else:
	GENE_SERVER_STDERR_FILE = open(GENE_SERVER_STDERR_FILE,'a')

if BCFEED_STDERR_FILE == "/dev/null":
	BCFEED_STDERR_FILE = fnull
else:
	BCFEED_STDERR_FILE = open(BCFEED_STDERR_FILE,'a')

if WC_SERVER_STDERR_FILE == "/dev/null":
	WC_SERVER_STDERR_FILE = fnull
else:
	WC_SERVER_STDERR_FILE = open(WC_SERVER_STDERR_FILE,'a')

if REPORT_GEN_STDERR_FILE == "/dev/null":
	REPORT_GEN_STDERR_FILE = fnull
else:
	REPORT_GEN_STDERR_FILE = open(REPORT_GEN_STDERR_FILE,'a')

if GTS_STDERR_FILE == "/dev/null":
	GTS_STDERR_FILE = fnull
else:
	GTS_STDERR_FILE = open(GTS_STDERR_FILE,'a')


#at least one client should not run with the get_config option to make sure new gene_def.json config files get loaded into the db.
monitored_launch = ['pypy gts.py all y run_once pid ',\
'pypy gts.py 1 y run_once get_config pid ',\
'pypy gts.py 2 y run_once get_config pid ',\
'pypy gts.py 3 y run_once get_config pid ',\
'pypy gts.py 4 y run_once get_config pid ',\
'pypy gts.py all y run_once get_default_config score_only pid ',\
'pypy gts.py all y run_once get_default_config pid ']

unmonitored_launch = ['python wc_server.py','python report_gen.py']

monitor = {}	#variables to track monitored/unmonitored processes
no_monitor = []

def terminate_process(process):
	if sys.platform == 'win32':
		import ctypes
		PROCESS_TERMINATE = 1
		pid = process.pid
		handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
		ctypes.windll.kernel32.TerminateProcess(handle, -1)
		ctypes.windll.kernel32.CloseHandle(handle)
	else:
		if process.poll() == None:
			process.terminate()
			process.wait()

# create and register callback function to do a clean shutdown of the system on exit.
def shutdown():
	global monitor
	global no_monitor
	global run_server

	for p in no_monitor:
		terminate_process(p)

	for pid in monitor.keys():
		terminate_process(monitor[pid]['process'])
	sys.stderr = fnull
	if run_server:
		server.shutdown()

atexit.register(shutdown)



if run_server:
	#update the dataset
	print "Synching the local datafeed..."
	Popen(shlex.split('python bcfeed_synch.py -d')).wait()

	#launch the bcfeed script to collect data from the live feed
	print "Starting the live datafeed capture script..."
	p = Popen(shlex.split('python bcfeed.py'),stdin=fnull, stdout=fnull, stderr=BCFEED_STDERR_FILE)
	no_monitor.append(p)

	print "Launching the xmlrpc server..."
	Popen(shlex.split('pypy gene_server.py'),stdin=fnull, stdout=fnull, stderr=GENE_SERVER_STDERR_FILE)
	sleep(1) #give the server time to start


# connect to the xml server
#
import gene_server_config
import xmlrpclib
import json
__server__ = gene_server_config.__server__
__port__ = str(gene_server_config.__port__)
server = xmlrpclib.Server('http://' + __server__ + ":" + __port__)  
print "Connected to",__server__,":",__port__


if run_server:
	a = ""
	while not(a == 'y' or a == 'n'):
		print "Load archived gene database? (y/n)"
		a = raw_input()

	if a == 'y':
		print "Loading the gene database..."
		print server.reload()


if run_client:
	print "Launching GA Clients..."

	#collect system process PIDS for monitoring. 
	#(not the same as system OS PIDs -- They are more like GUIDs as this is a multiclient distributed system) 
	epl = json.loads(server.pid_list()) #get the existing pid list

	#start the monitored processes
	for cmd_line in monitored_launch:
		new_pid = make_pid()
		p = Popen(shlex.split(cmd_line + new_pid),stdin=fnull, stdout=fnull, stderr=GTS_STDERR_FILE)
		retry = MONITORED_PROCESS_LAUNCH_TIMEOUT
		while retry > 0:
			sleep(1)
			cpl = json.loads(server.pid_list())	#get the current pid list
			npl = list(set(epl) ^ set(cpl)) 	#find the new pid(s)
			epl = cpl				#update the existing pid list
			if new_pid in npl:
				monitor.update({new_pid:{'cmd':cmd_line,'process':p}})	#store the pid/cmd_line/process
				print "Monitored Process Launched (PID:",new_pid,"CMD:",cmd_line,")"
				break
			else:
				retry -= 1
		if retry == 0:
			print "ERROR: Monitored Process Failed to Launch","(CMD:",cmd_line,")"
if run_server:
	#start unmonitored processes
	for cmd_line in unmonitored_launch:
		p = Popen(shlex.split(cmd_line),stdin=fnull, stdout=fnull, stderr=fnull)
		print "Unmonitored Process Launched (CMD:",cmd_line,")"
		no_monitor.append(p)	#store the popen instance
		sleep(1) #wait a while before starting the report_gen script


print "\nMonitoring Processes..."
count = 0
while 1:
	if run_server:
		count += 1
		#periodicaly tell the server to save the gene db
		if count == 10:
			count = 0
			server.save()
		if run_client == 0:
			sleep(30) 

	if run_client:
		#process monitor loop
		for pid in monitor.keys():
			sleep(5) #check one pid every n seconds.
			if server.pid_check(pid,WATCHDOG_TIMEOUT) == "NOK":
				#watchdog timed out
				print "WATCHDOG: PID",pid,"EXPIRED"
				#remove the expired PID
				server.pid_remove(pid)
				epl = json.loads(server.pid_list()) 	#get the current pid list
				cmd_line = monitor[pid]['cmd']
				#terminate the process
				terminate_process(monitor[pid]['process'])
				monitor.pop(pid)
				#launch new process
				new_pid = make_pid()
				p = Popen(shlex.split(cmd_line + new_pid),stdin=fnull, stdout=fnull, stderr=GTS_STDERR_FILE)
				retry = MONITORED_PROCESS_LAUNCH_TIMEOUT
				while retry > 0:
					sleep(1)
					cpl = json.loads(server.pid_list())	#get the current pid list
					npl = list(set(epl) ^ set(cpl)) 	#find the new pid(s)
					epl = cpl				#update the existing pid list
					if new_pid in npl:
						monitor.update({new_pid:{'cmd':cmd_line,'process':p}})	#store the pid/cmd_line/process
						print "Monitored Process Launched (PID:",new_pid,"CMD:",cmd_line,")"
						break
					else:
						retry -= 1
				if retry == 0:
					print "ERROR: Monitored Process Failed to Launch","(CMD:",cmd_line,")"		

fnull.close()
